package com.effectiv.gooruda.rest.service.api;

import java.util.Date;
import java.util.List;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;

import jodd.util.StringUtil;
import lombok.Getter;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.dao.DataAccessException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.CollectionCallback;

import com.effectiv.gooruda.rest.docs.annotation.Api;
import com.effectiv.gooruda.rest.docs.annotation.ApiBodyObject;
import com.effectiv.gooruda.rest.docs.annotation.ApiHeaders;
import com.effectiv.gooruda.rest.docs.annotation.ApiHeader;
import com.effectiv.gooruda.rest.docs.annotation.ApiParam;
import com.effectiv.gooruda.rest.docs.annotation.ApiResponseObject;

import com.effectiv.gooruda.rest.docs.annotation.ApiMethod;
import com.effectiv.gooruda.rest.docs.pojo.ApiVerb;
import com.effectiv.gooruda.rest.domain.Log;
import com.effectiv.gooruda.rest.ext.ExtResponse;
import com.effectiv.gooruda.rest.ext.SecurityContextHolder;

import com.mongodb.DBCollection;
import com.mongodb.MongoException;

@Path("/log")
@Api(name = "Log API", description = "Log Management API.")

public abstract class LogService {

	private final String COLLECTION_BASE = "c_app_log_";

	private final String ALLOWED_LOG_TYPES[] = { "DEBUG", "INFO", "WARN",
			"ERROR", "FATAL" };

	@Getter
	@Autowired
	private MongoTemplate template;

	protected void validateLogType(String type) {
		if (StringUtil.equalsOne(type, ALLOWED_LOG_TYPES) == -1) {

			ExtResponse extRes = new ExtResponse();
			extRes.setErrorCode(100);
			extRes.setMessage("Log type - "
					+ type
					+ " is not supported. Allowed values - DEBUG, INFO, WARN,ERROR, FATAL.");

			ResponseBuilder resBuilder = Response.status(Status.BAD_REQUEST);
			resBuilder.entity(extRes);

			throw new WebApplicationException(resBuilder.build());
		}
	}

	protected long countDocuments(final Query query) {
		return template.execute(getCollection(),
				new CollectionCallback<Long>() {

					public Long doInCollection(DBCollection collection)
							throws MongoException, DataAccessException {
						return collection.count(query.getQueryObject());
					}
				});
	}

	protected Response find(final Criteria criteria, int pageNo, int pageSize) {
		Query query = new Query();
		query.addCriteria(criteria);
		query.skip(pageNo * pageSize);
		query.limit(pageSize);

		List<Log> logs = getTemplate().find(query, Log.class, getCollection());

		ExtResponse res = new ExtResponse();
		res.setValue(logs);

		if (logs == null || logs.size() == 0) {
			res.setMessage("No log record found.");
		}

		GenericEntity<ExtResponse> entity = new GenericEntity<ExtResponse>(res,
				ExtResponse.class);

		ResponseBuilder builder = Response.ok(entity);

		return builder.build();
	}

	protected Response countDocuments(final Criteria criteria) {

		Query query = new Query();
		query.addCriteria(criteria);
		long count = countDocuments(query);

		ExtResponse res = new ExtResponse();
		res.setValue(count);

		GenericEntity<ExtResponse> entity = new GenericEntity<ExtResponse>(res,
				ExtResponse.class);

		ResponseBuilder builder = Response.ok(entity);

		return builder.build();

	}

	protected String getCollection() {
		return COLLECTION_BASE + SecurityContextHolder.get().getId();
	}

	/**
	 * Find all logs between two dates.
	 * 
	 * @param start
	 * @param end
	 * @param pageNo
	 * @param pageSize
	 * @return
	 */

	@ApiMethod(path = "/api/log/findbetween/{start}/{end}", verb = ApiVerb.GET, description = "Find all logs between two dates.", produces = { MediaType.APPLICATION_JSON }, consumes = { MediaType.APPLICATION_JSON })
	@ApiHeaders(headers = {
			@ApiHeader(name = "signature", description = "Authentication signature."),
			@ApiHeader(name = "public-key", description = "Public key of the application.") })
	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/findbetween/{start}/{end}")
	public abstract @ApiResponseObject
	Response findBetween(
			@PathParam("start") @ApiParam(name = "start", description = "Start Date") Date start,
			@PathParam("end") @ApiParam(name = "end", description = "End Date") Date end,
			@QueryParam("pageNo") @DefaultValue("0") @ApiParam(name = "pageNo", description = "Page No") int pageNo,
			@QueryParam("pageSize") @DefaultValue("10") @ApiParam(name = "pageSize", description = "Page Size") int pageSize);

	/**
	 * Find logs by text.
	 * 
	 * @param text
	 * @param pageNo
	 * @param pageSize
	 * @return
	 */

	@ApiMethod(path = "/api/log/findbytext/{text}", verb = ApiVerb.GET, description = "Find logs by text.Partial text also allowed.", produces = { MediaType.APPLICATION_JSON }, consumes = { MediaType.APPLICATION_JSON })
	@ApiHeaders(headers = {
			@ApiHeader(name = "signature", description = "Authentication signature."),
			@ApiHeader(name = "public-key", description = "Public key of the application.") })
	@GET
	@Path("/findbytext/{text}")
	@Produces(MediaType.APPLICATION_JSON)
	public abstract @ApiResponseObject
	Response findByText(
			@PathParam("text") @ApiParam(name = "text", description = "Text to search.") String text,
			@QueryParam("pageNo") @DefaultValue("0") @ApiParam(name = "pageNo", description = "Page No") int pageNo,
			@QueryParam("pageSize") @DefaultValue("10") @ApiParam(name = "pageSize", description = "Page Size") int pageSize);

	/**
	 * Save a new log entry.
	 * 
	 * @param l
	 * @return
	 */
	@ApiMethod(path = "/api/log/save", verb = ApiVerb.POST, description = "Save a new log entry.", produces = { MediaType.APPLICATION_JSON }, consumes = { MediaType.APPLICATION_JSON })
	@ApiHeaders(headers = {
			@ApiHeader(name = "signature", description = "Authentication signature."),
			@ApiHeader(name = "public-key", description = "Public key of the application.") })
	@POST
	@Produces(MediaType.APPLICATION_JSON)
	public abstract @ApiResponseObject
	Response save(@ApiBodyObject Log l);

	/**
	 * Find logs by type.
	 * 
	 * @param type
	 * @param pageNo
	 * @param pageSize
	 * @return
	 */

	@ApiMethod(path = "/api/log/findbytype/{type}", verb = ApiVerb.GET, description = "Find logs by type.", produces = { MediaType.APPLICATION_JSON }, consumes = { MediaType.APPLICATION_JSON })
	@ApiHeaders(headers = {
			@ApiHeader(name = "signature", description = "Authentication signature."),
			@ApiHeader(name = "public-key", description = "Public key of the application.") })
	@GET
	@Path("/findbytype/{type}")
	@Produces(MediaType.APPLICATION_JSON)
	public abstract @ApiResponseObject
	Response findByType(
			@PathParam("type") @ApiParam(name = "type", description = "Log type.", allowedvalues = { "DEBUG,INFO,ERROR,FATAL" }) String type,
			@QueryParam("pageNo") @DefaultValue("0") @ApiParam(name = "pageNo", description = "Page No") int pageNo,
			@QueryParam("pageSize") @DefaultValue("10") @ApiParam(name = "pageSize", description = "Page Size") int pageSize);

	/**
	 * Count the number of log entries by type.
	 * 
	 * @param type
	 * @return
	 */

	@ApiMethod(path = "/api/log/countbytype/{type}", verb = ApiVerb.GET, description = "Count the number of log entries by type.", produces = { MediaType.APPLICATION_JSON }, consumes = { MediaType.APPLICATION_JSON })
	@ApiHeaders(headers = {
			@ApiHeader(name = "signature", description = "Authentication signature."),
			@ApiHeader(name = "public-key", description = "Public key of the application.") })
	@GET
	@Path("/countbytype/{type}")
	@Produces(MediaType.APPLICATION_JSON)
	public abstract @ApiResponseObject
	Response countByType(
			@PathParam("type") @ApiParam(name = "type", description = "Log type.", allowedvalues = { "DEBUG,INFO,ERROR,FATAL" }) String type);

	/**
	 * Count the number of log entries by a text. Partial text also allowed.
	 * 
	 * @param text
	 * @return
	 */

	@ApiMethod(path = "/api/log/countbytype/{type}", verb = ApiVerb.GET, description = "Count the number of log entries by type.", produces = { MediaType.APPLICATION_JSON }, consumes = { MediaType.APPLICATION_JSON })
	@ApiHeaders(headers = {
			@ApiHeader(name = "signature", description = "Authentication signature."),
			@ApiHeader(name = "public-key", description = "Public key of the application.") })
	@GET
	@Path("/countbytext/{text}")
	@Produces(MediaType.APPLICATION_JSON)
	public abstract @ApiResponseObject
	Response countByText(
			@PathParam("type") @ApiParam(name = "type", description = "Log type.") String text);

	/**
	 * Counts the number of log entries between a date range.
	 * 
	 * @param start
	 * @param end
	 * @return
	 */

	@ApiMethod(path = "/api/log/countbytype/{type}", verb = ApiVerb.GET, description = "Counts the number of log entries between a date range.", produces = { MediaType.APPLICATION_JSON }, consumes = { MediaType.APPLICATION_JSON })
	@ApiHeaders(headers = {
			@ApiHeader(name = "signature", description = "Authentication signature."),
			@ApiHeader(name = "public-key", description = "Public key of the application.") })
	@GET
	@Path("/countbydaterange/{start}/{end}")
	@Produces(MediaType.APPLICATION_JSON)
	public abstract @ApiResponseObject
	Response countByDateRange(
			@PathParam("start") @ApiParam(name = "start", description = "Start Date") Date start,
			@PathParam("end") @ApiParam(name = "end", description = "End Date") Date end);
}
